package YottaTron;

import YottaTron.Sprites.Sprite;
import YottaTron.Sprites.PlayerSprite;
import YottaTron.Sprites.ObstacleSprite;
import YottaTron.Sprites.ProjectileSprite;
import YottaTron.Equipment.Engine.BasicEngine;
import YottaTron.Equipment.Gear.BasicGear;
import YottaTron.Equipment.Torso.BasicTorso;
import YottaTron.Equipment.Weapons.Minigun;
import YottaTron.Actions.*;

import YottaTron.Actions.Action;
import YottaTron.Equipment.Weapons.DoubleRL;
import YottaTron.Equipment.Weapons.Weapon;
import YottaTron.communication.CommunicationUDP;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import YottaTron.Map.*;
import YottaTron.Sprites.BotSprite;
import java.awt.AlphaComposite;
import java.util.ConcurrentModificationException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;
import org.lwjgl.util.Timer;

/**
 *
 * @author Cmunda
 */
public class Runner extends JPanel implements Runnable, MouseListener, MouseMotionListener, KeyListener, TickEvent {

    /* hodilo by se sjednotit konstanty, v editoru je potreba jedine pole grafiky typu GraphicsLoader,
     * kde je treba davat pozor na to, jestli nemaji ruzne konstanty stejnou hodnotu
     * a kdyz se tady zavedou nadbytecne konstanty napr. GSP (spriteGraphics) tak budou v poli spriteGraphics
     * zbytecne prazdna mista
     * podle me by se dalo staticGraphics a spriteGraphics klidne sloucit co ty na to?
     */

    //static graphic
    public static final int GST_BACKGROUND1 = 0;
    public static final int GST_ROCK = 1;

    // sprite graphics
    public static final int GSP_ROBOT = 0;
    public static final int GSP_MINIGUN_PROJECTILE = 1;
    public static final int GSP_ROCKET_PROJECTILE = 2;
    public static final int GSP_ROCKET_LAUNCHER = 3;
    public static final int GSP_MINIGUN = 4;
    public static final int GSP_DOUBLE_ROCKET_LAUNCHER = 5;
    public static final int GSP_ROCK = 6;
    public static final int GSP_TREE0 = 7;
    public static final int GSP_TREE1 = 8;
    public static final int GSP_TREE2 = 9;
    public static final int GSP_TREE3 = 10;
    public static final int GSP_TREE4 = 11;

    // graphics
    public static final int GD_TREE = 0;
    public static final int TREE_BURNING_ANIMATION = 6;

    // animation graphic
    public static final int GA_MINIGUN_SHOOTING = 0;
    public static final int GA_EXPLOSION = 1;
    public static final int GA_BASIC_GEAR = 2;
    public static final int GA_ROCKET_LAUNCHER_SHOOTING = 3;
    public static final int GA_MINIGUN_PROJECTILE_HIT = 4;
    public static final int GA_ROCKET_LAUNCHER_PROJECTILE_HIT = 5;
    public static final int GA_FIRE = 6;
    public static final int GA_SMOKE = 7;

    // animations
    public static final int A_MINIGUN_SHOOTING = 0;
    public static final int A_BASIC_GEAR_MOVING = 1;
    public static final int A_ROCKET_LAUNCHER_SHOOTING = 2;
    //pozadovane fps - kvuli pauze v threadu, pokud by nebyla pauza reapint by sezral vsechen cas
    public static final int desiredFPS = 60;
    public static final int AIM_RESOLUTION = 5000;
    //pevne dany casovy usek pouzivany pro update herni fyziky
    public static final float GameUpdateTime = 0.033f;
    public static final String graphicsDir = "Graphics/";
    //velmi presny casovac
    public static org.lwjgl.util.Timer globalTimer = new Timer();
    //casovy usek mezi vykreslovanymy snimky - kvuli animacim
    public final double globalTickTime = 1d / (double) desiredFPS;
    public int[][] herniPole;
    public int width,  height,  tileWidth,  tileHeight,  playerWidth,  playerHeight;
    public ArrayList<PlayerSprite> players;
    public ArrayList<BotSprite> bots;
    public ArrayList<ProjectileSprite> projectiles;
    public ArrayList<ObstacleSprite> obstacles;
    public ArrayList<ObstacleSprite> burningObstacles;
    public int numObjectsOnMap;
    private int numProjectiles;
    private int numPlayers = 0;
    private int fps;
    private int ups;
    private GraphicsLoader[] staticGraphics;
    private GraphicsLoader[] spriteGraphics;
    private GraphicsLoader[][] destructibleObstacleGraphics;
    public AnimatedGraphicsLoader[] animationGraphics;
    private ImagesPlayer[] animations;
    private ArrayList<ImagesPlayer> staticAnimations = new ArrayList<ImagesPlayer>();
    private int numVTiles;
    private int numHTiles;
    public boolean client;
    public Map map;
    public boolean clientReady = false;
    public boolean mapReady = false;
    public boolean gameInfo = false;
    public CommunicationUDP comm;
    GraphicsLoader emptyTileGraphics;
    GraphicsLoader fullTileGraphics;
    GraphicsLoader backgroundImage;
    YotTimer aimSendTimeout;
    public int framesSkipped = 0;
    public float accumulator;
    public float time;
    public float currentTime;
    public float deltaTime;
    public boolean handleSynchronize;
    public PlayerSprite playerToCatchUp = null;

    public Runner(boolean client) {
        this.client = client;
        this.width = 800;
        this.height = 600;
        players = new ArrayList();
        bots = new ArrayList();
        projectiles = new ArrayList();
        burningObstacles = new ArrayList();

        // packety s aimem se budou posilat po 100ms
        aimSendTimeout = new YotTimer(this, 50);

        /* pokud se hra spousti na strane klienta - zaregistrujeme listenery, nacteme grafiku
         * nastavi se velikost platna a barva pozadi
         */
//        if (client) {
        //Nahrani obrazku
        this.addMouseMotionListener(this);
        this.addKeyListener(this);
        this.addMouseListener(this);
        initializeGraphics();

        this.setPreferredSize(new Dimension(width, height));
        this.setBackground(Color.WHITE);
        playerWidth = spriteGraphics[GSP_ROBOT].getImage(0).getWidth();
        playerHeight = spriteGraphics[GSP_ROBOT].getImage(0).getHeight();
        System.out.println(playerWidth);
//        } else {
//            playerWidth = 100;
//            playerHeight = 100;
//        }

        if (!client) {
            //generateMap();
            map = new Map("test1.map");
            //vykreslime mapu na herni plochu - musi si vygenerovat i server
            generateTerrainFromMap(map);

        }
    //na zacatku musi server poslat kientum stav vytvorene hry

//        editorPanel = new EditorPanel();
//        this.add(editorPanel);

    }// KONEC KONSTRUKTORU

    public void initializeGraphics() {

        try {
            emptyTileGraphics = new GraphicsLoader(graphicsDir + "tile_empty.png");
            fullTileGraphics = new GraphicsLoader(graphicsDir + "tile_full.png");
            GraphicsLoader gBackground = new GraphicsLoader(graphicsDir + "backgrounds/background1.png");
            GraphicsLoader gRock = new GraphicsLoader(graphicsDir + "obstacles/rock.png");
            GraphicsLoader gMinigunProjectile = new GraphicsLoader(graphicsDir + "weapons/minigunProjectile.png");
            RotatingGraphicsLoader grBasicTorso = new RotatingGraphicsLoader(graphicsDir + "torsos/basicTorso.png", graphicsDir + "torsos/basicTorsoShape.png");
            RotatingGraphicsLoader grRocket = new RotatingGraphicsLoader(graphicsDir + "weapons/rocket0.png");
            RotatingGraphicsLoader grRocketLauncher = new RotatingGraphicsLoader(graphicsDir + "weapons/rocketLauncher.png");
            RotatingGraphicsLoader grMinigun = new RotatingGraphicsLoader(graphicsDir + "weapons/Minigun.png");
            RotatingGraphicsLoader grDoubleRL = new RotatingGraphicsLoader(graphicsDir + "weapons/doubleRL.png");
            RotatingGraphicsLoader grTree0 = new RotatingGraphicsLoader(graphicsDir + "obstacles/tree0.png");
            RotatingGraphicsLoader grTree1 = new RotatingGraphicsLoader(graphicsDir + "obstacles/tree1.png");
            RotatingGraphicsLoader grTree2 = new RotatingGraphicsLoader(graphicsDir + "obstacles/tree2.png");
            RotatingGraphicsLoader grTree3 = new RotatingGraphicsLoader(graphicsDir + "obstacles/tree3.png");
            RotatingGraphicsLoader grTree4 = new RotatingGraphicsLoader(graphicsDir + "obstacles/tree4.png");
            RotatingAnimatedGraphicsLoader graMinigunShooting = new RotatingAnimatedGraphicsLoader(graphicsDir + "weapons/minigunShootingAnimation.png", 2);
            RotatingAnimatedGraphicsLoader graRocketLauncherShooting = new RotatingAnimatedGraphicsLoader(graphicsDir + "weapons/rocketLauncherShootingAnimation.png", 2);
            RotatingAnimatedGraphicsLoader graBasicGear = new RotatingAnimatedGraphicsLoader(graphicsDir + "gears/basicGear.png", 3);
            //animace vybuchu hrace
            AnimatedGraphicsLoader gaExplosion = new AnimatedGraphicsLoader(graphicsDir + "explosionAnimation.png", 7);
            AnimatedGraphicsLoader gaFire = new AnimatedGraphicsLoader(graphicsDir + "fireAnimation.png", 7);
            gaFire.alpha = 0.7f;
            //animace zasahu projektilem
            AnimatedGraphicsLoader gaMinigunProjectileHit = new AnimatedGraphicsLoader(graphicsDir + "weapons/minigunProjectileHitAnimation.png", 3);
            AnimatedGraphicsLoader gaRocketLauncherProjectileHit = new AnimatedGraphicsLoader(graphicsDir + "weapons/rocketLauncherHitAnimation.png", 7);
            //kour
            AnimatedGraphicsLoader gaSmoke = new AnimatedGraphicsLoader(graphicsDir + "smoke.png", 10);
            gaSmoke.alpha = 0.4f;
            GraphicsLoader[] gTree = new GraphicsLoader[5];
            for (int i = 0; i < 5; i++) {
                gTree[i] = new GraphicsLoader(graphicsDir + "obstacles/tree" + i + ".png");
            }
            destructibleObstacleGraphics = new GraphicsLoader[10][5];
            destructibleObstacleGraphics[GD_TREE] = gTree;
            staticGraphics = new GraphicsLoader[2];
            staticGraphics[GST_BACKGROUND1] = gBackground;
            staticGraphics[GST_ROCK] = gRock;
            spriteGraphics = new GraphicsLoader[15];
            spriteGraphics[GSP_ROBOT] = grBasicTorso;
            spriteGraphics[GSP_MINIGUN_PROJECTILE] = gMinigunProjectile;
            spriteGraphics[GSP_ROCKET_PROJECTILE] = grRocket;
            spriteGraphics[GSP_ROCKET_LAUNCHER] = grRocketLauncher;
            spriteGraphics[GSP_MINIGUN] = grMinigun;
            spriteGraphics[GSP_DOUBLE_ROCKET_LAUNCHER] = grDoubleRL;
            spriteGraphics[GSP_ROCK] = gRock;
            spriteGraphics[GSP_TREE0] = grTree0;
            spriteGraphics[GSP_TREE1] = grTree1;
            spriteGraphics[GSP_TREE2] = grTree2;
            spriteGraphics[GSP_TREE3] = grTree3;
            spriteGraphics[GSP_TREE4] = grTree4;
            animationGraphics = new AnimatedGraphicsLoader[10];
            animationGraphics[GA_MINIGUN_SHOOTING] = graMinigunShooting;
            animationGraphics[GA_EXPLOSION] = gaExplosion;
            animationGraphics[GA_BASIC_GEAR] = graBasicGear;
            animationGraphics[GA_ROCKET_LAUNCHER_SHOOTING] = graRocketLauncherShooting;
            animationGraphics[GA_MINIGUN_PROJECTILE_HIT] = gaMinigunProjectileHit;
            animationGraphics[GA_ROCKET_LAUNCHER_PROJECTILE_HIT] = gaRocketLauncherProjectileHit;
            animationGraphics[GA_FIRE] = gaFire;
            animationGraphics[GA_SMOKE] = gaSmoke;
            double updateTime = 1d / 60d;
            ImagesPlayer ipMinigunShooting = new ImagesPlayer(animationGraphics[0], 0.2, 1, updateTime, true, this);
            ImagesPlayer ipRocketLauncherShooting = new ImagesPlayer(animationGraphics[3], 0.2, 1, updateTime, false, this);
            ImagesPlayer ipBasicGearMoving = new ImagesPlayer(animationGraphics[2], 0.2, 1, updateTime, true, this);
            animations = new ImagesPlayer[5];
            animations[A_MINIGUN_SHOOTING] = ipMinigunShooting;
            animations[A_BASIC_GEAR_MOVING] = ipBasicGearMoving;
            animations[A_ROCKET_LAUNCHER_SHOOTING] = ipRocketLauncherShooting;
        } catch (IOException ex) {
            Logger.getLogger(Runner.class.getName()).log(Level.SEVERE, null, ex);
        }

        System.out.println("nanotime " + System.nanoTime());

    }

    // tuto metodu je treba yavolat pred prvnim pouzitim comm ... pred zavolanim runner.start()
    public void addComm(CommunicationUDP comm) {
        this.comm = comm;
    }

    //tato metoda umisti na hraci plochu prekazky
    public void generateMap() {
        int obstacleID = 10000;
        map = new Map("backgrounds/background1.png", GST_BACKGROUND1, width, height, tileWidth, tileHeight);
        for (int i = 0; i < numHTiles; i++) {
            for (int j = 0; j < numVTiles; j++) {
                if (i == 0 || i == numHTiles - 1) {
                    map.obstacles.add(new Obstacle(obstacleID,
                            i * tileWidth, j * tileHeight,
                            100, GST_ROCK, -1, -1, false, false, false));
                    obstacleID++;
                }
                if (j == 0 || j == numVTiles - 1) {

                    map.obstacles.add(new Obstacle(obstacleID,
                            i * tileWidth, j * tileHeight,
                            100, GST_ROCK, -1, -1, false, false, false));
                    obstacleID++;
                }
            //System.out.println(herniPole[i][j]);
            }
        }
        for (int i = 0; i < 20; i++) {
            map.obstacles.add(new Obstacle(obstacleID,
                    tilesToX((int) (Math.random() * (numHTiles - 2) + 1)),
                    tilesToY((int) (Math.random() * (numVTiles - 2) + 1)),
                    50, GD_TREE, -1, TREE_BURNING_ANIMATION, true, true, true));
            obstacleID++;
        }

        map.numObjects = obstacleID - 10000 + 1;
//        obstacles = new ArrayList<ObstacleSprite>();
//
//
//
//        GraphicsLoader[] rockGraphics = new GraphicsLoader[5];
//        for (int i = 0; i < 5; i++) {
//            rockGraphics[i] = new GraphicsLoader(graphicsDir + "rock.png");
//
//        }
//
//        int obstacleID = 10000;
//        for (int i = 0; i < numHTiles; i++) {
//            for (int j = 0; j < numVTiles; j++) {
//
//                if (i == 0 || i == numHTiles - 1) {
//
//                    obstacles.add(new ObstacleSprite(i * tileWidth, j * tileHeight, 25, 25, obstacleID, rockGraphics, this));
//                    obstacleID++;
//                }
//                if (j == 0 || j == numVTiles - 1) {
//
//                    obstacles.add(new ObstacleSprite(i * tileWidth, j * tileHeight, 25, 25, obstacleID, rockGraphics, this));
//                    obstacleID++;
//                }
//            //System.out.println(herniPole[i][j]);
//            }
//        }
//        for (int i = 0; i < 20; i++) {
//            ObstacleSprite tree = new ObstacleSprite(tilesToX((int) (Math.random() * (numHTiles - 2) + 1)), tilesToY((int) (Math.random() * (numVTiles - 2) + 1)), 25, 25, obstacleID, treeGraphics, this);
//            tree.setDamageable(true);
//            tree.setDestructible(true);
//            tree.setMaxHitPoints(50);
//            tree.setBurningAnimation(new ImagesPlayer(animationGraphics.get(6), 0.5, 1, globalTickTime, true, this));
//            obstacles.add(tree);
//            obstacleID++;
//
//        }
        byte[] ba = null;
        try {

            //zjisteni velikosti objektu obstacles v bytech

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(map);
            oos.close();
            ba = bos.toByteArray();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();

        }
        System.out.println(ba.length);


    }

    public void generateTerrainFromMap(Map map) {
        try {
            int obstacleID = 10000;
            tileWidth = map.tileWidth;
            tileHeight = map.tileHeight;
            width = map.width;
            height = map.height;
            this.setPreferredSize(new Dimension(width, height));
            numVTiles = height / tileHeight;
            numHTiles = width / tileWidth;
            obstacles = new ArrayList<ObstacleSprite>();
            herniPole = map.gameField;
            System.out.println("map background name: " + map.backgroundName);
//graphicsDir + "backgrounds/background1.png"
            GraphicsLoader gBackground = new GraphicsLoader(graphicsDir + "backgrounds/" + map.backgroundName);
            staticGraphics[GST_BACKGROUND1] = gBackground;
            backgroundImage = staticGraphics[map.backgroundGraphics];

            for (Iterator<Obstacle> it = map.obstacles.iterator(); it.hasNext();) {
                Obstacle o = it.next();
                ObstacleSprite obstacle = null;
                switch (o.obstacleGraphics) {
                    case GSP_ROCK:
                        obstacle = new ObstacleSprite(o.x, o.y, obstacleID, spriteGraphics[GSP_ROCK], this);
                        obstacle.setDamageable(false);
                        obstacle.setDestructible(false);
                        obstacle.setIgnitable(false);
                        break;
                    case GSP_TREE0:
                        obstacle = new ObstacleSprite(o.x, o.y, o.ID, destructibleObstacleGraphics[GD_TREE], this);
                        break;
                    case GSP_TREE1:
                        obstacle = new ObstacleSprite(o.x, o.y, o.ID, destructibleObstacleGraphics[GD_TREE], this);
                        break;
                    case GSP_TREE2:
                        obstacle = new ObstacleSprite(o.x, o.y, o.ID, destructibleObstacleGraphics[GD_TREE], this);
                        break;
                    case GSP_TREE3:
                        obstacle = new ObstacleSprite(o.x, o.y, o.ID, destructibleObstacleGraphics[GD_TREE], this);
                        break;
                    case GSP_TREE4:
                        obstacle = new ObstacleSprite(o.x, o.y, o.ID, destructibleObstacleGraphics[GD_TREE], this);
                        break;
                }
                if (o.obstacleGraphics != GSP_ROCK) {
                    obstacle.setDamageable(true);
                    obstacle.setDestructible(true);
                    obstacle.setIgnitable(true);
                    obstacle.setBurningAnimation(new ImagesPlayer(animationGraphics[TREE_BURNING_ANIMATION], 0.5, 1, globalTickTime, true, this));
                }


//                if (o.obstacleAnimations == -1) {
//                    if (o.damageable) {
//                        obstacle = new ObstacleSprite(o.x, o.y, o.ID, destructibleObstacleGraphics[o.obstacleGraphics], this);
//                    } else {
//                        obstacle = new ObstacleSprite(o.x, o.y, o.ID, staticGraphics[o.obstacleGraphics], this);
//                    }
//                } else {
//                    //zde pridat podporu pro animovane prekazky
//                }
//                if (o.damageable) {
//                    obstacle.setDamageable(true);
//                }
//                if (o.destructible) {
//                    obstacle.setDestructible(true);
//                }
//                if (o.ignitable) {
//                    obstacle.setIgnitable(true);
//                    obstacle.setBurningAnimation(new ImagesPlayer(animationGraphics[TREE_BURNING_ANIMATION], 0.5, 1, globalTickTime, true, this));
//                }

                obstacle.setMaxHitPoints(100);
                obstacles.add(obstacle);
                populate(obstacle);
                clientReady = true;
            }
        } catch (IOException ex) {
            Logger.getLogger(Runner.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void run() {
        int count = 0;
        int countUps = 0;
        globalTimer.tick();
        currentTime = globalTimer.getTime();
        time = 0;
        float start = currentTime;
        float interval = 1;
        requestFocusInWindow();


        if (client) {

            //cekame nez server posle mapu
            while (!mapReady) {
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                }
            }

            //vykreslime mapu na herni plochu - musi si vygenerovat i server
            //na tomto miste se musi zpracovat prijata mapa, aby se uvazovala spravna width a height
            generateTerrainFromMap(map);

            //pridame hrace
            double playerStartingPositionX = Math.random() * (width - 100) + 50;
            double playerStartingPositionY = Math.random() * (height - 100) + 50;
            double playerDirection = Math.random() * Math.PI * 2;
            // odpovedi serveru na NEW_PLAYER je odeslani  PLAYERS se seynamem vsech dosud pripojenych hracu
            PlayerSprite player = new PlayerSprite(playerStartingPositionX, playerStartingPositionY, playerWidth, playerHeight, comm.clientID, this);
            player.addEngine(new BasicEngine());
            player.addGear(new BasicGear(animations[1]));
            player.addTorso(new BasicTorso((RotatingGraphicsLoader) spriteGraphics[GSP_ROBOT]));

            player.addWeapon(new Minigun((RotatingGraphicsLoader) spriteGraphics[GSP_MINIGUN], spriteGraphics[GSP_MINIGUN_PROJECTILE], animations[0], animationGraphics[4]));
            //        player.addWeapon(new RocketLauncher((RotatingGraphicsLoader) spriteGraphics.get(ROCKET_LAUNCHER_GRAPHICS), (RotatingGraphicsLoader)spriteGraphics.get(ROCKET_PROJECTILE_GRAPHICS), animations.get(2)));
            player.addWeapon(new DoubleRL((RotatingGraphicsLoader) spriteGraphics[GSP_DOUBLE_ROCKET_LAUNCHER], (RotatingGraphicsLoader) spriteGraphics[GSP_ROCKET_PROJECTILE], animations[2], animationGraphics[5]));
            numPlayers++;
            comm.sendNewPlayer(playerStartingPositionX, playerStartingPositionY, playerDirection, player.weaponsToArray());
            // robot ovladany hracem musi byt na nulte pozici
            // (pro pripad, kdyby byly prihlaseni jini hraci pres sit)
            // zmena..ovladatelny hrac ma polozku controllable
            player.controllable = true;
            players.add(player);

//            //pridame jednoho inteligentniho bota
//            double botStartingPositionX = Math.random() * (width - 100) + 50;
//            double botStartingPositionY = Math.random() * (height - 100) + 50;
//
//            BotSprite bot = new BotSprite(botStartingPositionX, botStartingPositionY, playerWidth, playerHeight, comm.clientID+100, this);
//            bot.addEngine(new BasicEngine());
//            bot.addGear(new BasicGear(new ImagesPlayer(animationGraphics[2], 0.2, 1, globalTickTime, true, this)));
//            bot.addTorso(new BasicTorso((RotatingGraphicsLoader) spriteGraphics[GSP_ROBOT]));
//
//            bot.addWeapon(new Minigun((RotatingGraphicsLoader) spriteGraphics[GSP_MINIGUN], spriteGraphics[GSP_MINIGUN_PROJECTILE], animations[0], animationGraphics[4]));
//            //        player.addWeapon(new RocketLauncher((RotatingGraphicsLoader) spriteGraphics.get(ROCKET_LAUNCHER_GRAPHICS), (RotatingGraphicsLoader)spriteGraphics.get(ROCKET_PROJECTILE_GRAPHICS), animations.get(2)));
//            bot.addWeapon(new DoubleRL((RotatingGraphicsLoader) spriteGraphics[GSP_DOUBLE_ROCKET_LAUNCHER], (RotatingGraphicsLoader) spriteGraphics[GSP_ROCKET_PROJECTILE], animations[2], animationGraphics[5]));
//            bot.strategy = new RandomStrategy(bot, this);
//            bots.add(bot);


        }

        clientReady = true;

        while (true) {


            //provede udapte herniho sveta, pohyb spritu, kolize, posun animaci

            Timer.tick();
            //System.out.println(deltaTime);
            float newTime = globalTimer.getTime();
            deltaTime = newTime - currentTime;
            currentTime = newTime;



            accumulator += deltaTime;

            while (accumulator >= GameUpdateTime) {
                countUps++;
                if (client) {
                    ClientGameUpdate(count, time, GameUpdateTime);
                } else {
                    ServerGameUpdate(count, time, GameUpdateTime);
                }
                time += GameUpdateTime;
                accumulator -= GameUpdateTime;

            }
            //po ubehnuti intervalu (1s) spocitame hodnoty FPS a UPS
            if (currentTime >= start + interval) {
                start = currentTime;
                fps = count;
                ups = countUps;
                count = 0;
                countUps = 0;
            }
            //pocitame cykly repaintu
            count++;
            this.repaint();
            //pauza aby si pan procesor odpocinul
            try {
                globalTimer.tick();
                long pause = 1000 / desiredFPS - (long) ((globalTimer.getTime() - currentTime) * 1000);


                Thread.sleep(pause);


            } catch (Exception e) {
                //e.printStackTrace();
                }

        }
    }

    
    //provede veskere vypocty prubehu hry na strane Serveru
    //parametr count se predava jako udaj o ubehlem case pro pripad ze by neco bylo potreba vykonavat v urcitych casovych usecich
    //parametr time urcuje aktualni cas v dobe updatu
    //parametr DT urcuje deltaTime od predchoziho updatu (nyni fixni)
    public void ServerGameUpdate(int count, float time, float DT) {
        //pohybujeme animacemi
        animationsUpdate(count);

        //Obslouzime pohyb prjektilu - musi byt provadeno na strane serveru stejne jako na strane klienta
        //rozhodujici je pohyb strel na strane serveru - ten urcuje zasahy a damage
        for (int i = 0; i < numProjectiles; i++) {

            Sprite s = projectiles.get(i);
            Action action = s.getAction();
            //pokud strela vykonava nakou akci
            if (action != null) {
                //pokud akce skoncila odebereme ji
                if (!action.nextStep(DT)) {
                    s.removeAction();
                }
            }

            for (int j = 0; j < s.getParalelActions().size(); j++) {
                Action act = s.getParalelActions().get(j);
                if (!act.nextStep(DT)) {
                    s.removeParalelAction(act);
                }

            }

            //posuneme fyziku
            handleSpritePhysics(s, DT);
        }// konec obsluhy projektilu


        //obsluha botu - musi byt provadeno na strane serveru stejne jako na strane klienta
        for (int i = 0; i < bots.size(); i++) {

            BotSprite b = bots.get(i);
            dePopulate(b);
            b.strategy.checkBehavior();
            Action action = b.getAction();
            //pokud vykonava nakou akci
            if (action != null) {
                //pokud akce skoncila odebereme ji
                if (!action.nextStep(DT)) {
                    b.removeAction();
                }
            }
            for (int j = 0; j < b.getParalelActions().size(); j++) {
                Action act = b.getParalelActions().get(j);
                if (!act.nextStep(DT)) {
                    b.removeParalelAction(act);
                }

            }
            if (b.burning && ((count % (int) (Math.random() * 10 + 5)) == 0)) {
                ShowAnimationAt(new ImagesPlayer(animationGraphics[7], 0.5, 1, globalTickTime, false, this),
                        (int) (b.getCenterX() + Math.random() * 5 - Math.random() * 5) - animationGraphics[7].images[0].getWidth() / 2,
                        (int) (b.getY() + Math.random() * 5 - Math.random() * 5) - animationGraphics[7].images[0].getHeight() + playerHeight / 2);
            }
            //posuneme fyziku
            handleSpritePhysics(b, DT);

            if (b.isActive()) {
                populate(b);
            }

        }//konec obsluhy botu

    }

    //provede veskere vypocty prubehu hry na strane Klienta
    //parametr count se predava jako udaj o ubehlem case pro pripad ze by neco bylo potreba vykonavat v urcitych casovych usecich
    //parametr time urcuje aktualni cas v dobe updatu
    //parametr DT urcuje deltaTime od predchoziho updatu (nyni fixni)

    public void ClientGameUpdate(int count, float time, float DT) {
        //pohybujeme animacemi
        animationsUpdate(count);
        
        //pokud vlakno communicationUDP prijme data k synchronizaci, nastavi handleSynchronize=true a tady synchronizujeme
        try {
            if (handleSynchronize) {
                //pro nami ovladaneho hrace se pouzije predikce z historie pohybu
                for (Iterator<PlayerSprite> it = players.iterator(); it.hasNext();) {
                    PlayerSprite playerSprite = it.next();
                    if (playerSprite.controllable) {
                        //client prediction
                        catchUPWithServer(playerSprite);
                     //ostatni hraci se synchronizuji bez predikce
                    } else {
                        //pokusime se vyhladit skoky v pohybu ciziho hrace (posun pouze o frakci skoku)
                        dePopulate(playerSprite);
                        //rozdil mezi aktulani pozici u nas a tou co prisla ze serveru
                        double xDiff = playerSprite.serverX - playerSprite.x;
                        //pokud je rozdil velky, posuneme hrace rovnou na spravne misto
                        if (Math.abs(xDiff) >20) {
                            playerSprite.x = playerSprite.serverX;
                            System.out.println("snapping");
                        //pokud je skok do nejake hranice, posuneme ho pouze o frakci (dohanime server) coz pusobi plynule
                        } else if(Math.abs(xDiff)>1) {
                            playerSprite.x += 0.2 * xDiff;
                            System.out.println("smoothing");
                        //pokud je hrac tam kde ma byt nedelame nic
                        }else{
                        playerSprite.x = playerSprite.serverX;
                            System.out.println("correct");
                        }
                        //to same pro Y
                        double yDiff = playerSprite.serverY - playerSprite.y;
                        if (Math.abs(yDiff) >20) {
                            playerSprite.y = playerSprite.serverY;
                            System.out.println("snapping");
                        } else if(Math.abs(yDiff)>1) {
                            playerSprite.y += 0.2 * yDiff;
                            System.out.println("smoothing");
                        }else{
                        playerSprite.y = playerSprite.serverY;
                            System.out.println("correct");
                        }


                        //smer a rychlost nastavime rovnou presne podle serveru - neni to tak videt a je dulezitejsi je mit presne
                        playerSprite.setSpeed(playerSprite.serverSpeed);
                        playerSprite.changeDirection(playerSprite.serverDirection);
                        playerSprite.setAimAngle((double) playerSprite.netAimAngle / AIM_RESOLUTION);
                        playerSprite.currentTime = playerSprite.synchroTime;

                 
                    }
                }
            }


            //obsluha hracu
            for (Iterator<PlayerSprite> it = players.iterator(); it.hasNext();) {

                PlayerSprite p = it.next();

                if (p.controllable) {
                    //obslouzime vstupy
                    handleControllablePlayer(p, time);
                    //nastavime aim podle pozice mysi
                    setAim(p.aimX, p.aimY);
                     //akce provadime pouze u lokalniho hrace
                    Action action = p.getAction();

                    //pokud vykonava nakou akci
                    if (action != null) {
                        //pokud akce skoncila odebereme ji
                        if (!action.nextStep(DT)) {
                            p.removeAction();
                        }
                    }
                  
                    //vykoname paralelni akce
                    for (int j = 0; j < p.getParalelActions().size(); j++) {
                       Action act = p.getParalelActions().get(j);

                        if (!act.nextStep(DT)) {
                            p.removeParalelAction(act);
                            //System.out.println("removing action");
                            j--;
                        }

                    }
                }
                //pokud hrac zije
                if (!p.dead) {
                    //odstranime ho z herni plochy
                    dePopulate(p);
                    //kdyz hrac hori poustime na jeho pozici v intervalech animace koure
                    if (p.burning && ((count % (int) (Math.random() * 10 + 5)) == 0)) {
                        ShowAnimationAt(new ImagesPlayer(animationGraphics[7], 0.5, 1, globalTickTime, false, this), (int) (p.getCenterX() + Math.random() * 5 - Math.random() * 5) - animationGraphics[7].images[0].getWidth() / 2, (int) (p.getY() + Math.random() * 5 - Math.random() * 5) - animationGraphics[7].images[0].getHeight() + playerHeight / 2);

                    }
                    //pokud se pohybuje tocime koly
                    if (p.getSpeed() != 0) {
                        if (!p.gear.animationPlayer.playing) {
                            p.gear.animationPlayer.playing = true;


                        }
                    } else {
                        p.gear.animationPlayer.playing = false;
                    }


                    //posuneme cooldowny na zbranich
                    handlePlayerCooldowns(p, DT);
                    //strilime
                    if (p.shooting) {
                        playerShoot(p);
                    }
                    //vypocteme novou polohu a osetrime kolize
                    handleSpritePhysics(p, DT);
                    //nastavime novy cas
                    p.currentTime = time;

                    
                    if (p.controllable) {
                        //ulozime do historie pohyby lokalniho hrace, kvuli predikci pohybu
                        p.movementHistory.add(new MovementVector(p.speed, p.direction, DT, time - DT));
                        //posleme serveru informaci o zmene vektoru pohybu - zakladni prvek komunikace - server na zaklade toho udpatuje fyziku na sve strane
                        comm.sendClientPacket(time);
                    }


                    // opet umistime hrace na herni plochu
                    if (p.isActive()) {
                        populate(p);
                    }

                } else {
                    //pokud je hrac mrtev znovu ho narodime
                    if (!client) {
                        p.respawn();
                    }
                }
            }
        } catch (ConcurrentModificationException e) {
        }
        //konec obsluhy hrace

        for (int i = 0; i < numProjectiles; i++) {
            Sprite s = projectiles.get(i);
            Action action = s.getAction();
            //pokud vykonava nakou akci
            if (action != null) {
                //pokud akce skoncila odebereme ji
                if (!action.nextStep(DT)) {
                    s.removeAction();
                }
            }

            for (int j = 0; j < s.getParalelActions().size(); j++) {
                Action act = s.getParalelActions().get(j);
                if (!act.nextStep(DT)) {
                    s.removeParalelAction(act);
                }

            }
//            System.out.println("DT " + DT);

            handleSpritePhysics(s, DT);
        }// konec obsluhy projektilu

        //obsluha botu
        for (int i = 0; i < bots.size(); i++) {

            BotSprite b = bots.get(i);
            dePopulate(b);
            b.strategy.checkBehavior();
            Action action = b.getAction();
            //pokud vykonava nakou akci
            if (action != null) {
                //pokud akce skoncila odebereme ji
                if (!action.nextStep(DT)) {
                    b.removeAction();
                }
            }
            for (int j = 0; j < b.getParalelActions().size(); j++) {
                Action act = b.getParalelActions().get(j);
                if (!act.nextStep(DT)) {
                    b.removeParalelAction(act);
                }

            }
            if (b.burning && ((count % (int) (Math.random() * 10 + 5)) == 0)) {
                ShowAnimationAt(new ImagesPlayer(animationGraphics[7], 0.5, 1, globalTickTime, false, this),
                        (int) (b.getCenterX() + Math.random() * 5 - Math.random() * 5) - animationGraphics[7].images[0].getWidth() / 2,
                        (int) (b.getY() + Math.random() * 5 - Math.random() * 5) - animationGraphics[7].images[0].getHeight() + playerHeight / 2);
            }

            handleSpritePhysics(b, DT);

            if (b.isActive()) {
                populate(b);
            }

        }


    }

    //meni obrazky u animaci
    //count - pocitadlo s rychlosti fps - bere se z clientGameUpdate nebo serverGameUpdate
    public void animationsUpdate(int count){
     ImagesPlayer player = null;//objekt pro reuse uvnitr cyklu

        //posun animatoru
        for (int i = 0; i < animations.length; i++) {
            player = animations[i];
            if (player != null) {
                player.updateImage();
            }
        }
        //posuneme staticke animace (vybuchy, ohen, kour)
        for (int i = 0; i < staticAnimations.size(); i++) {
            player = staticAnimations.get(i);
            if (player != null) {
                player.updateImage();
            }
        }
         //nechame horet prekazky (animace i ubirani zivotu)
        //tento blok se provadi provadi priblizne FPS/10x za sekundu a kazdou celou sekundu
        if (count % (int) (Math.random() * 10 + 5) == 0) {
            for (int i = 0; i < burningObstacles.size(); i++) {
                ObstacleSprite o = burningObstacles.get(i);
                if (count == 0) {
                    o.damage(2);
                }
                ShowAnimationAt(new ImagesPlayer(animationGraphics[7], Math.random() * 0.5 + 0.25, 1, globalTickTime, false, this), (int) (o.getX() + Math.random() * 5 - Math.random() * 5), (int) (o.getY() + Math.random() * 5 - Math.random() * 5) - animationGraphics[7].images[0].getHeight());

            }
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        if (clientReady) {

//            g2.setColor(Color.GREEN);
            g2.drawImage(backgroundImage.getImage(0), 0, 0, null);
//            g2.fillRect(0, 0, width, height);

            //vykresleni dlazdic - tiles
//            for (int i = 0; i < numVTiles; i++) {
//                for (int j = 0; j < numHTiles; j++) {
//                    if (herniPole[j][i] == 0) {
//                        //g2.drawImage(emptyTileGraphics.im, j * tileWidth, i * tileHeight, tileWidth, tileHeight, null);
//                        //char[] text = Integer.toString(0).toCharArray();
//                        //g2.setColor(Color.RED);
//                        //g2.drawChars(text, 0, text.length, j * tileWidth, i * tileHeight);
//                    } else {
//                        //g2.drawImage(fullTileGraphics.im, j * tileWidth, i * tileHeight, tileWidth, tileHeight, null);
//                        char[] text = Integer.toString(herniPole[j][i]).toCharArray();
//                        g2.setColor(Color.RED);
//                        g2.drawChars(text, 0, text.length, j * tileWidth, i * tileHeight);
//                    }
//                }
//            }


//            for (Iterator<ObstacleSprite> it = obstacles.iterator(); it.hasNext();) {
//                ObstacleSprite obstacle = it.next();
//                g2.drawImage(obstacle.obstacleGraphic.getImage(0), (int) obstacle.getX(), (int) obstacle.getY(), null);
//            }

            // kvuli concurent modification
            ObstacleSprite obstacle;
            for (int i = 0; i < obstacles.size(); i++) {
                obstacle = obstacles.get(i);
                if (obstacle != null) {
                    g2.drawImage(obstacle.obstacleGraphic.getImage(0), (int) obstacle.getX(), (int) obstacle.getY(), null);
                }
            }

            //vykreslime hrace - jejich robota - postupne podle vybaveni
            for (int i = 0; i < numPlayers; i++) {
                //System.out.println("vykresluji hrace cislo " + i);
                PlayerSprite o = players.get(i);
//                System.out.println("id " + o.ID + " active " + o.active + " dead " + o.dead);
                if (o.active && !o.dead) {
                    //nejdrive podvozek
                    g2.drawImage(o.gear.animationPlayer.getCurrentImage(o.getDirection()), (int) o.getX(), (int) o.getY(), null);
                    //pote torso
                    g2.drawImage(o.torso.graphics.getImage(o.getDirection()), (int) o.getX(), (int) o.getY(), null);
                    //zbran

                    for (Iterator it = o.getWeapons().iterator(); it.hasNext();) {
                        Weapon weapon = (Weapon) it.next();
                        g2.drawImage(weapon.weaponGraphics.getImage(o.getAimAngle()), (int) o.getX(), (int) o.getY(), null);
                        if (weapon.getShootingAnimation().playing) {
                            g2.drawImage(weapon.getShootingAnimation().getCurrentImage(o.getAimAngle()), (int) o.getX(), (int) o.getY(), null);
                        }

                    }

                    //            char[] text = Integer.toString(((PlayerSprite) o).getHitPoints()).toCharArray();
                    int hitPoints = o.getHitPoints();
                    if (hitPoints > 80) {
                        g2.setColor(Color.GREEN);
                    } else if (hitPoints > 60) {
                        g2.setColor(Color.YELLOW);
                    } else if (hitPoints > 40) {
                        g2.setColor(Color.ORANGE);
                    } else {
                        g2.setColor(Color.RED);
                    }
//            g2.drawChars(text, 0, text.length, (int) o.getX(), (int) o.getY());
                    g2.fillRect((int) o.getX(), (int) o.getY(), o.getHitPoints(), 4);
                }
            }
            //vykreslime boty
            for (int i = 0; i < bots.size(); i++) {
                BotSprite o = bots.get(i);
                //nejdrive podvozek
                g2.drawImage(o.gear.animationPlayer.getCurrentImage(o.getDirection()), (int) o.getX(), (int) o.getY(), null);
                //pote torso
                g2.drawImage(o.torso.graphics.getImage(o.getDirection()), (int) o.getX(), (int) o.getY(), null);
                //zbran

                for (Iterator it = o.getWeapons().iterator(); it.hasNext();) {
                    Weapon weapon = (Weapon) it.next();
                    g2.drawImage(weapon.weaponGraphics.getImage(o.getAimAngle()), (int) o.getX(), (int) o.getY(), null);
                    if (weapon.getShootingAnimation().playing) {
                        g2.drawImage(weapon.getShootingAnimation().getCurrentImage(o.getAimAngle()), (int) o.getX(), (int) o.getY(), null);
                    }

                }


                //            char[] text = Integer.toString(((PlayerSprite) o).getHitPoints()).toCharArray();
                int hitPoints = o.getHitPoints();
                if (hitPoints > 80) {
                    g2.setColor(Color.GREEN);
                } else if (hitPoints > 60) {
                    g2.setColor(Color.YELLOW);
                } else if (hitPoints > 40) {
                    g2.setColor(Color.ORANGE);
                } else {
                    g2.setColor(Color.RED);
                }
//            g2.drawChars(text, 0, text.length, (int) o.getX(), (int) o.getY());
                g2.fillRect((int) o.getX(), (int) o.getY(), o.getHitPoints(), 4);
            }
            //vykreslime strely
            for (int i = 0; i < projectiles.size(); i++) {
                ProjectileSprite o = projectiles.get(i);
                g2.drawImage(o.projectileGraphics.getImage(o.getDirection()), (int) o.getX(), (int) o.getY(), null);
            }
            char[] text = Integer.toString(fps).toCharArray();
            g2.setColor(Color.RED);
            g2.drawChars(text, 0, text.length, 30, 30);

            text = Integer.toString(ups).toCharArray();
            g2.setColor(Color.RED);
            g2.drawChars(text, 0, text.length, 30, 50);

            g2.setColor(Color.BLACK);
            g2.fillRect(map.width, 0, 500, 1000);
            g2.fillRect(0, map.height, 1000, 500);

            ImagesPlayer player = null;
            //vykreslime ostatni animace

            for (int i = 0; i < staticAnimations.size(); i++) {
                player = staticAnimations.get(i);
                if (player != null) {
                    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, player.sequence.alpha));
                    g2.drawImage(player.getCurrentImage(), player.getX(), player.getY(), this);
                }
            }




        } else {//jeste neprisla mapa
            g2.setColor(Color.WHITE);
            g2.fillRect(0, 0, width, height);
            char[] text = new String("Loading map from server...").toCharArray();
            g2.setColor(Color.RED);
            g2.drawChars(text, 0, text.length, width / 2, height / 2);
            drawIncommingDataLoader(g2, width / 2, height / 2 + 30, comm.hugeDataReceived, comm.hugeDataExpected);
        }

        if (gameInfo) {
            paintGameInfo(g2);
        }
//        editorPanel.repaint();

    }// konec paintComponent

    public void drawIncommingDataLoader(Graphics2D g, int x, int y, int actual, int total) {
        g.setColor(Color.BLACK);
        g.fillRect(x, y, 100, 10);
        g.setColor(Color.RED);
        g.fillRect(x, y, (int) ((double) actual / total * 100), 10);
    }

    public void paintGameInfo(Graphics2D g2) {
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f));
        g2.setColor(Color.BLACK);
        g2.fillRect(0, 0, width, height);
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
        char[] text = new String("GAME INFO").toCharArray();
        g2.setColor(Color.RED);
        g2.drawChars(text, 0, text.length, width / 2, 50);

        text = new String("Player").toCharArray();
        g2.setColor(Color.RED);
        g2.drawChars(text, 0, text.length, width / 2 - 100, 70);

        text = new String("Frags").toCharArray();
        g2.setColor(Color.RED);
        g2.drawChars(text, 0, text.length, width / 2, 70);

        text = new String("Deaths").toCharArray();
        g2.setColor(Color.RED);
        g2.drawChars(text, 0, text.length, width / 2 + 50, 70);

        for (int i = 0; i < players.size(); i++) {
            PlayerSprite player = players.get(i);
            text = Integer.toString(player.ID).toCharArray();
            g2.setColor(Color.RED);
            g2.drawChars(text, 0, text.length, width / 2 - 100, 70 + (i + 1) * 20);

            text = Integer.toString(player.frags).toCharArray();
            g2.setColor(Color.RED);
            g2.drawChars(text, 0, text.length, width / 2, 70 + (i + 1) * 20);

            text = Integer.toString(player.deaths).toCharArray();
            g2.setColor(Color.RED);
            g2.drawChars(text, 0, text.length, width / 2 + 50, 70 + (i + 1) * 20);

        }

        text = Integer.toString(comm.packetsSent).toCharArray();
        g2.setColor(Color.RED);
        g2.drawChars(text, 0, text.length, width / 2 + 50, 300);
    }

    //Co se stane pri kolizi
    public void kolize(Sprite o, Point where) {
        o.handleCollision(getSpriteAt((int) where.getX(), (int) where.getY()));
    }


    //Colision CHECK
    public Point isKolize(Sprite o, double newX, double newY) {
        List shape = o.getShape();
        for (Iterator iter = shape.iterator(); iter.hasNext();) {
            Point2D point = (Point2D) iter.next();
            int x = XToTiles((int) (point.getX() + newX));
            int y = YToTiles((int) (point.getY() + newY));
            if (herniPole[x][y] > 0 && herniPole[x][y] != o.ID) {
                return new Point(x, y);
            }
        }

        return null;
    }

    public int XToTiles(int x) {
        if (x >= width) {
            //System.out.println("XMAX");
            return numHTiles - 1;
        }
        if (x < 0) {
            return 0;
        }
        return (int) (x / tileWidth);
    }

    public int YToTiles(int y) {
        int coord = (int) Math.floor(y / tileHeight);
        if (coord >= numVTiles) {
            //System.out.println("YMAX");
            return numVTiles - 1;
        }
        if (coord < 0) {
            return 0;
        }
        return coord;
    }

    public int tilesToX(int x) {
        if (x >= width) {
            return width - tileWidth;
        }
        if (x < 0) {
            return 0;
        }
        return (x * tileWidth);
    }

    public int tilesToY(int y) {
        if (y >= height) {
            return height - tileHeight;
        }
        if (y < 0) {
            return 0;
        }
        return y * tileHeight;
    }

    public void populate(Sprite o) {
        List shape = o.getShape();

        //zaplnime policka na kterych je pritomen shape Spritu
        //POKUD to nejsou pevne prekazky - hrac nemuze pri sikovne rotaci mazat pevne objekty
        for (Iterator iter = shape.iterator(); iter.hasNext();) {
            Point2D point = (Point2D) iter.next();
            if (herniPole[XToTiles((int) (point.getX() + o.getX()))][YToTiles((int) (point.getY() + o.getY()))] < 10000) {
                herniPole[XToTiles((int) (point.getX() + o.getX()))][YToTiles((int) (point.getY() + o.getY()))] = o.getID();
            }
        }
    }

    public void dePopulate(Sprite o) {
        List shape = o.getShape();

        //vyprazdnime policka na kterych je pritomen shape Spritu
        //POKUD to nejsou pevne prekazky - hrac nemuze pri sikovne rotaci mazat pevne objekty
        for (Iterator iter = shape.iterator(); iter.hasNext();) {
            Point2D point = (Point2D) iter.next();
            if (herniPole[XToTiles((int) (point.getX() + o.getX()))][YToTiles((int) (point.getY() + o.getY()))] < 10000) {
                herniPole[XToTiles((int) (point.getX() + o.getX()))][YToTiles((int) (point.getY() + o.getY()))] = 0;
            }
        //System.out.println("policko " + XToTiles((int) (point.getX() + o.getX())) + "/" + YToTiles((int) (point.getY() + o.getY())) + "je vyprazdneno");
        }


    }

    public void addPlayer(double x, double y, double direction, int clientID, byte[] weapons) {
        PlayerSprite player = new PlayerSprite(x, y, playerWidth, playerHeight, clientID, this);

        player.addEngine(new BasicEngine());
        player.addGear(new BasicGear(animations[1]));
        player.addTorso(new BasicTorso((RotatingGraphicsLoader) spriteGraphics[GSP_ROBOT]));

        player.addWeapons(weapons, spriteGraphics, animations, animationGraphics);

        players.add(player);
        numPlayers++;
        //lastID = player.ID;
        System.out.println("addPlayer");
    }

    public void addPlayer(PlayerSprite o) {
        players.add(o);
        numPlayers++;
    }

    public void removePlayer(Sprite o) {
        players.remove(o);
        numPlayers--;
    // System.out.println("removed object " + o.getClass().getName());
    }

    public void removePlayer(int id) {
        players.remove(getSpriteByID(id));
        numPlayers--;
    // System.out.println("removed object " + o.getClass().getName());
    }

    public void addProjectile(ProjectileSprite o) {
        projectiles.add(o);
        numProjectiles++;
    }

    public void removeProjectile(Sprite o) {
        projectiles.remove(o);
        numProjectiles--;
    // System.out.println("removed object " + o.getClass().getName());
    }

    public void addObstacle(ObstacleSprite o) {
        obstacles.add(o);
    }

    public void removeObstacle(ObstacleSprite o) {
        List shape = o.getShape();
        for (Iterator iter = shape.iterator(); iter.hasNext();) {
            Point2D point = (Point2D) iter.next();
            herniPole[XToTiles((int) (point.getX() + o.getX()))][YToTiles((int) (point.getY() + o.getY()))] = 0;

        }
        obstacles.remove(o);
    }

    public void removeBot(BotSprite o) {
        bots.remove(o);
    }

    public Sprite getSpriteAt(int x, int y) {
        Sprite sprite = null;
        //System.out.println("getting sprite ID " + herniPole[XToTiles(x)][XToTiles(y)]);
        for (int i = 0; i < players.size(); i++) {
            if (players.get(i).getID() == herniPole[x][y]) {
                sprite = players.get(i);
            }
        }
        for (int i = 0; i < obstacles.size(); i++) {
            if (obstacles.get(i).getID() == herniPole[x][y]) {
                sprite = obstacles.get(i);
            }
        }
        for (int i = 0; i < bots.size(); i++) {
            if (bots.get(i).getID() == herniPole[x][y]) {
                sprite = bots.get(i);
            }
        }
        return sprite;
    }

    public PlayerSprite getSpriteByID(int ID) {
        PlayerSprite sprite = null;
        for (Iterator iter = players.iterator(); iter.hasNext();) {
            sprite = (PlayerSprite) iter.next();
            if (sprite.getID() == ID) {
                return sprite;
            }
        }
        return null;
    }

    public PlayerSprite getControllablePlayer() {
        PlayerSprite sprite = null;
        for (Iterator iter = players.iterator(); iter.hasNext();) {
            sprite = (PlayerSprite) iter.next();
            if (sprite.controllable == true) {
                return sprite;
            }
        }
        return null;
    }

    public void mouseClicked(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

    public void mousePressed(MouseEvent e) {
        requestFocusInWindow();
        PlayerSprite player = getControllablePlayer();
        if (player != null) {
            if (e.getButton() == e.BUTTON1) {
                if (!player.isMouseLeftPressed()) {
                    player.setMouseLeftPressed(true);
                    player.mouseLeftPressed_last = false;
                }
            } else {
            }
        }
    }

    public void mouseReleased(MouseEvent e) {
        PlayerSprite player = getControllablePlayer();
        if (player != null) {
            if (e.getButton() == e.BUTTON1) {
                if (player.isMouseLeftPressed()) {
                    player.setMouseLeftPressed(false);
                    player.mouseLeftPressed_last = true;
                }
            } else {
            }
        }

    }

    public void mouseDragged(MouseEvent e) {
        PlayerSprite player = getControllablePlayer();
        if (player != null) {
            player.aimX = e.getX();
            player.aimY = e.getY();
            player.variableChanged = true;
        }
    //setAim(e.getX(), e.getY());
    }

    public void mouseMoved(MouseEvent e) {
        PlayerSprite player = getControllablePlayer();
        if (player != null) {
            player.aimX = e.getX();
            player.aimY = e.getY();
            player.variableChanged = true;
        }
    // setAim(e.getX(), e.getY());
    }

    public void setAim(int x, int y) {
        PlayerSprite player = getControllablePlayer();
        if (player != null) {
            player.aimX = x;
            player.aimY = y;
            player.setAimAngle();// vypocet getAimAngle
            //prevedeme aimAngle aby se dal poslat jako byte s co nejvetsi presnosti
            player.netAimAngle = (short) (player.getAimAngle() * AIM_RESOLUTION);
            player.setAimAngle((double) player.netAimAngle / AIM_RESOLUTION); // stejny vypocet jako u prijateho packetu
        }
    }

    //prepsanej eventHandler jenom infromuje o prave stisknutejch klavesach, samotny reakce jsou az v handleControllablePlayer
    public void keyPressed(KeyEvent e) {
        PlayerSprite player = getControllablePlayer();
        if (player != null) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_UP:

                    if (!player.isKey_up()) {
                        player.key_up_last = false;
                        player.setKey_up(true);
                    }
                    break;
                case KeyEvent.VK_DOWN:

                    if (!player.isKey_down()) {


                        player.key_down_last = false;
                        player.setKey_down(true);
            
                    }
                    break;
                case KeyEvent.VK_LEFT:

                    if (!player.isKey_left()) {
                        player.setKey_left(true);
                        player.key_left_last = false;
                    }
                    break;
                case KeyEvent.VK_RIGHT:

                    if (!player.isKey_right()) {
                        player.setKey_right(true);
                        player.key_right_last = false;
                    }
                    break;
                case KeyEvent.VK_F5: // sory, nefunguje mi F1:)
                    if (!gameInfo) {
                        gameInfo = true;
                    } else {
                        gameInfo = false;
                    }
                    break;

            }

        }
    }

    public void keyReleased(KeyEvent e) {
        PlayerSprite p = getControllablePlayer();
        if (p != null) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_UP:

                    //comm.sendKeyUp((byte) 0, p.getX(), p.getY(), p.getDirection());
                    if (p.isKey_up()) {

                        p.setKey_up(false);
                        p.key_up_last = true;
                    }


                    break;

                case KeyEvent.VK_DOWN:
                    if (p.isKey_down()) {


                        p.setKey_down(false);
                        p.key_down_last = true;
                    }
//                    comm.sendKeyDown((byte) 0, p.getX(), p.getY(), p.getDirection());

                    break;

                case KeyEvent.VK_LEFT:
                    if (p.isKey_left()) {


                        p.setKey_left(false);
                        p.key_left_last = true;
                    }
//                    comm.sendKeyLeft((byte) 0, p.getX(), p.getY(), p.getDirection());

                    break;

                case KeyEvent.VK_RIGHT:
                    if (p.isKey_right()) {


                        p.setKey_right(false);
                        p.key_right_last = true;
                    }
//                    comm.sendKeyRight((byte) 0, p.getX(), p.getY(), p.getDirection());
                    //System.out.println("removing turn action");
                    break;

            }


        }
    }

    public void keyTyped(KeyEvent e) {
    }


    //vola se kazdej gameUpdate
    //obsluhuje stisknute a pustene klavesy
    public void handleControllablePlayer(PlayerSprite player, float currentTime) {

        if (player.isKey_up() && player.key_up_last == false) {
            player.setParalelAction(new AccelerateAction(player, currentTime, this));
            player.removeParalelAction(Action.BRAKE_ACTION, currentTime);
            player.key_up_last = true;
        }

        if (player.isKey_down() && player.key_down_last == false) {
            //System.out.println("adding reverse action");
            player.setParalelAction(new ReverseAction(player, currentTime, this));
            player.removeParalelAction(Action.BRAKE_ACTION, currentTime);
            player.key_down_last = true;
        }

        if (player.isKey_left() && player.key_left_last == false) {
            //System.out.println("adding turn left action");
            player.setParalelAction(new TurnAction(player, -1, currentTime, this));
            player.key_left_last = true;
        }
        if (player.isKey_right() && player.key_right_last == false) {
            //System.out.println("adding turn right action");
            player.setParalelAction(new TurnAction(player, 1, currentTime, this));
            player.key_right_last = true;
        }

        if (!player.isKey_up() && player.key_up_last == true) {
            //System.out.println("removing accelerate action");
            player.removeParalelAction(Action.ACCELERATE_ACTION, currentTime);
            player.setParalelAction(new BrakeAction(player, currentTime, this));
            player.key_up_last = false;
        }

        if (!player.isKey_down() && player.key_down_last == true) {
            //System.out.println("removing reverse action");
            player.removeParalelAction(Action.REVERSE_ACTION, currentTime);
            player.setParalelAction(new BrakeAction(player, currentTime, this));
            player.key_down_last = false;
        }
        if (!player.isKey_left() && player.key_left_last == true) {
            player.removeParalelAction(Action.TURN_LEFT_ACTION, currentTime);
            player.key_left_last = false;
        }
        if (!player.isKey_right() && player.key_right_last == true) {
            player.removeParalelAction(Action.TURN_RIGHT_ACTION, currentTime);
            player.key_right_last = false;
        }
        if (player.isMouseLeftPressed()) {

            player.mouseLeftPressed_last = true;
        }

        if (!player.isMouseLeftPressed() && player.mouseLeftPressed_last) {
            System.out.println("finished shooting");

            player.mouseLeftPressed_last = false;
        }

    }

    public ArrayList<PlayerSprite> getPlayers() {
        return players;
    }

    public ArrayList<ProjectileSprite> getProjectiles() {
        return projectiles;
    }

    public GraphicsLoader[] getSpriteGraphics() {
        return spriteGraphics;
    }

    public GraphicsLoader[] getStaticGraphics() {
        return staticGraphics;
    }

    public void disposeAnimation(ImagesPlayer animation) {
        if (animation.staticAnimation) {
            staticAnimations.remove(animation);
        } else {
            //animations.remove(this);
        }
    }

    public void addAnimation(ImagesPlayer animation) {
        //animations.add(animation);
    }

    public AnimatedGraphicsLoader[] getAnimationGraphics() {
        return animationGraphics;
    }

    public void ShowAnimationAt(ImagesPlayer player, int x, int y) {
        player.setAnimationLocation(x, y);
        player.startAnimation();
        staticAnimations.add(player);
    }

    public void tickEvent() {
        // zde se nevyuziva
    }


    //vola se po synchronizaci se serverem
    //opravi pozici hrace a pouzije predikci pohybu k odstraneni lagu
    public void catchUPWithServer(PlayerSprite player) {
        //najdeme tick ve kterem se udalost udala

        if (!player.movementHistory.isEmpty()) {
            dePopulate(player);
            //projizdime historii pohybu
            for (int i = 0; i < player.movementHistory.size(); i++) {
                MovementVector move = player.movementHistory.get(i);
                //zahodime pohyby co sou starsi nez synchroTime ty uz enbudeme potrebovat
                if (move.timeStamp < player.synchroTime) {
                    player.movementHistory.remove(move);
                    i--;
                }
                //prvni pohyb vykonany po synchroTime, pred nim nastavime spravnou pozici obdrzenou od serveru
                if (move.timeStamp == player.synchroTime) {
                    player.setX(player.serverX);
                    player.setY(player.serverY);
                    handleSpritePhysics(player, GameUpdateTime);
                }
                //pak zopakujeme vsechny dalsi pohyby
                if (move.timeStamp > player.synchroTime) {
                    player.changeDirection(move.direction);
                    player.setSpeed(move.speed);
                    handleSpritePhysics(player, GameUpdateTime);
                }

            }
            //teprve po prepocitani predikovane pozice umistime hrace na pllochu
            populate(player);

        }
        handleSynchronize = false;
    }


    //zakladni metoda
    //pohybuje sprity
    public void handleSpritePhysics(Sprite sprite, float deltaTime) {


        double newX = sprite.getX() + sprite.getDx() * deltaTime;
        double newY = sprite.getY() + sprite.getDy() * deltaTime;
        //System.out.println(newX + " " + newY + " " + sprite.getDx() +" "+ sprite.getDy() +" " + deltaTime);
        Point where = isKolize(sprite, newX, newY);
        if (where == null) {
            sprite.setX(newX);
            sprite.setY(newY);

        } else {

            kolize(sprite, where);
        }


    }

    //posunuje cooldowny na zbranich hrace
    public void handlePlayerCooldowns(PlayerSprite player, float time) {
        for (Iterator<Weapon> it = player.weapons.iterator(); it.hasNext();) {
            Weapon weapon = it.next();
            if (weapon.getCurrentCoolDown() > 0) {
                weapon.setCurrentCoolDown(weapon.getCurrentCoolDown() - time);
            }
        }
    }

    //provadi strelbu hrace
    public void playerShoot(PlayerSprite sprite) {
        double goalAngle = sprite.getAimAngle();
        for (Iterator<Weapon> it = sprite.weapons.iterator(); it.hasNext();) {
            Weapon weapon = it.next();


            if (weapon.getCurrentCoolDown() <= 0) {
                weapon.setCurrentCoolDown(weapon.getCoolDownTime());
                int projectileWidth = weapon.getProjectileGraphics().getImage(0).getWidth();
                int projectileHeight = weapon.getProjectileGraphics().getImage(0).getWidth();
                if (weapon.numProjectiles == 1) {
                    addProjectile(new ProjectileSprite(sprite.ID, sprite.getCenterX() + 20 * Math.sin(goalAngle) - projectileWidth / 2,
                            sprite.getCenterY() - 20 * Math.cos(goalAngle) - projectileHeight / 2,
                            projectileWidth, projectileHeight, goalAngle,
                            weapon.getProjectileSpeed(), weapon.getProjectileGraphics(),
                            weapon.projectileHitAnimation, weapon.getDamage(), weapon.incendiary, this));
                }
                if (weapon.numProjectiles == 2) {
                    addProjectile(new ProjectileSprite(sprite.ID,
                            sprite.getCenterX() + 20 * Math.sin(goalAngle) - projectileWidth / 2 - 10 * Math.cos(goalAngle),
                            sprite.getCenterY() - 20 * Math.cos(goalAngle) - projectileHeight / 2 - 10 * Math.sin(goalAngle),
                            projectileWidth, projectileHeight, goalAngle, weapon.getProjectileSpeed(), weapon.getProjectileGraphics(),
                            weapon.projectileHitAnimation, weapon.getDamage(), weapon.incendiary, this));
                    addProjectile(new ProjectileSprite(sprite.ID,
                            sprite.getCenterX() + 20 * Math.sin(goalAngle) - projectileWidth / 2 + 10 * Math.cos(goalAngle),
                            sprite.getCenterY() - 20 * Math.cos(goalAngle) - projectileHeight / 2 + 10 * Math.sin(goalAngle),
                            projectileWidth, projectileHeight, goalAngle, weapon.getProjectileSpeed(), weapon.getProjectileGraphics(),
                            weapon.projectileHitAnimation, weapon.getDamage(), weapon.incendiary, this));
                }
            }
        }
    }
}



